comark 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/devtools/index.d.ts +1 -0
- package/dist/devtools/index.js +1 -0
- package/dist/devtools/register.d.ts +1 -0
- package/dist/devtools/register.js +1 -0
- package/dist/devtools/registry.d.ts +1 -0
- package/dist/devtools/registry.js +1 -0
- package/dist/devtools/vite.d.ts +1 -0
- package/dist/devtools/vite.js +1 -0
- package/dist/internal/frontmatter.d.ts +1 -0
- package/dist/internal/frontmatter.js +4 -2
- package/dist/internal/parse/auto-close/index.js +25 -13
- package/dist/internal/parse/auto-close/table.js +12 -9
- package/dist/internal/parse/auto-unwrap.js +2 -10
- package/dist/internal/parse/html/html_block_rule.js +1 -1
- package/dist/internal/parse/html/html_inline_rule.js +3 -7
- package/dist/internal/parse/html/html_re.js +1 -1
- package/dist/internal/parse/html/index.js +14 -2
- package/dist/internal/parse/syntax/block-params.d.ts +9 -0
- package/dist/internal/parse/syntax/block-params.js +48 -0
- package/dist/internal/parse/syntax/brackets.d.ts +8 -0
- package/dist/internal/parse/syntax/brackets.js +20 -0
- package/dist/internal/parse/syntax/props.d.ts +5 -0
- package/dist/internal/parse/syntax/props.js +119 -0
- package/dist/internal/parse/token-processor.js +25 -24
- package/dist/internal/props-validation.js +4 -9
- package/dist/internal/stringify/attributes.js +4 -1
- package/dist/internal/stringify/handlers/a.js +1 -3
- package/dist/internal/stringify/handlers/blockquote.js +2 -4
- package/dist/internal/stringify/handlers/code.js +1 -3
- package/dist/internal/stringify/handlers/emphesis.js +1 -3
- package/dist/internal/stringify/handlers/html.js +26 -16
- package/dist/internal/stringify/handlers/img.js +1 -3
- package/dist/internal/stringify/handlers/li.js +14 -8
- package/dist/internal/stringify/handlers/mdc.js +2 -3
- package/dist/internal/stringify/handlers/ol.js +1 -1
- package/dist/internal/stringify/handlers/p.d.ts +1 -1
- package/dist/internal/stringify/handlers/p.js +4 -1
- package/dist/internal/stringify/handlers/pre.js +10 -13
- package/dist/internal/stringify/handlers/strong.js +1 -3
- package/dist/internal/stringify/handlers/table.js +7 -5
- package/dist/internal/stringify/handlers/template.js +1 -1
- package/dist/internal/stringify/handlers/ul.js +1 -1
- package/dist/internal/stringify/indent.d.ts +1 -5
- package/dist/internal/stringify/indent.js +1 -9
- package/dist/internal/stringify/state.js +1 -1
- package/dist/internal/yaml.js +1 -1
- package/dist/parse.js +14 -8
- package/dist/plugins/alert.js +1 -1
- package/dist/plugins/binding.js +1 -3
- package/dist/plugins/breaks.js +1 -1
- package/dist/plugins/emoji.js +8 -8
- package/dist/plugins/footnotes.js +19 -13
- package/dist/plugins/headings.js +2 -4
- package/dist/plugins/highlight.d.ts +1 -11
- package/dist/plugins/highlight.js +198 -103
- package/dist/plugins/json-render.js +5 -9
- package/dist/plugins/math.js +4 -6
- package/dist/plugins/mermaid.js +6 -20
- package/dist/plugins/punctuation.js +5 -6
- package/dist/plugins/security.js +2 -2
- package/dist/plugins/syntax.d.ts +49 -0
- package/dist/plugins/syntax.js +522 -0
- package/dist/plugins/task-list.d.ts +1 -1
- package/dist/plugins/task-list.js +11 -8
- package/dist/plugins/toc.js +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/utils/comark.tmLanguage.d.ts +335 -0
- package/dist/utils/comark.tmLanguage.js +597 -0
- package/dist/utils/helpers.js +1 -3
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +25 -3
- package/package.json +49 -51
- package/skills/skills/comark/AGENTS.md +0 -261
- package/skills/skills/comark/SKILL.md +0 -489
- package/skills/skills/comark/references/markdown-syntax.md +0 -599
- package/skills/skills/comark/references/parsing-ast.md +0 -378
- package/skills/skills/comark/references/rendering-react.md +0 -445
- package/skills/skills/comark/references/rendering-svelte.md +0 -453
- package/skills/skills/comark/references/rendering-vue.md +0 -462
- /package/skills/{skills/migrate-mdc-to-comark → migrate-mdc-to-comark}/SKILL.md +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Comark team and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../src/devtools/index'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../src/devtools/index.ts'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../src/devtools/register'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../src/devtools/register.ts'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../src/devtools/registry'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../src/devtools/registry.ts'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../src/devtools/vite'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../src/devtools/vite.ts'
|
|
@@ -9,11 +9,12 @@ const CR = '\r';
|
|
|
9
9
|
*/
|
|
10
10
|
export function parseFrontmatter(content) {
|
|
11
11
|
let data = {};
|
|
12
|
+
let frontmatter = '';
|
|
12
13
|
if (content.startsWith(FRONTMATTER_DELIMITER_DEFAULT)) {
|
|
13
14
|
const idx = content.indexOf(LF + FRONTMATTER_DELIMITER_DEFAULT);
|
|
14
15
|
if (idx !== -1) {
|
|
15
16
|
const hasCarriageReturn = content[idx - 1] === CR;
|
|
16
|
-
|
|
17
|
+
frontmatter = content.slice(4, idx - (hasCarriageReturn ? 1 : 0));
|
|
17
18
|
if (frontmatter) {
|
|
18
19
|
data = parseYaml(frontmatter);
|
|
19
20
|
content = content.slice(idx + 4 + (hasCarriageReturn ? 1 : 0));
|
|
@@ -23,6 +24,7 @@ export function parseFrontmatter(content) {
|
|
|
23
24
|
return {
|
|
24
25
|
content,
|
|
25
26
|
data,
|
|
27
|
+
frontmatterText: frontmatter,
|
|
26
28
|
};
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
@@ -33,7 +35,7 @@ export function parseFrontmatter(content) {
|
|
|
33
35
|
*/
|
|
34
36
|
export function renderFrontmatter(data, content, yamlOptions) {
|
|
35
37
|
if (!data || Object.keys(data).length === 0) {
|
|
36
|
-
return
|
|
38
|
+
return content?.trim() || '';
|
|
37
39
|
}
|
|
38
40
|
const fm = stringifyYaml(data, yamlOptions).trim();
|
|
39
41
|
if (content) {
|
|
@@ -77,11 +77,22 @@ export function autoCloseMarkdown(markdown) {
|
|
|
77
77
|
let nameEnd = colonCount;
|
|
78
78
|
while (nameEnd < trimmed.length) {
|
|
79
79
|
const c = trimmed[nameEnd];
|
|
80
|
-
if (!((c >= 'a' && c <= 'z') ||
|
|
80
|
+
if (!((c >= 'a' && c <= 'z') ||
|
|
81
|
+
(c >= 'A' && c <= 'Z') ||
|
|
82
|
+
(c >= '0' && c <= '9') ||
|
|
83
|
+
c === '$' ||
|
|
84
|
+
c === '.' ||
|
|
85
|
+
c === '-' ||
|
|
86
|
+
c === '_'))
|
|
81
87
|
break;
|
|
82
88
|
nameEnd++;
|
|
83
89
|
}
|
|
84
|
-
componentStack.push({
|
|
90
|
+
componentStack.push({
|
|
91
|
+
depth: colonCount,
|
|
92
|
+
name: trimmed.slice(colonCount, nameEnd),
|
|
93
|
+
indent,
|
|
94
|
+
hasYamlProps: false,
|
|
95
|
+
});
|
|
85
96
|
}
|
|
86
97
|
else if (colonCount === trimmed.length && componentStack.length > 0) {
|
|
87
98
|
const top = componentStack[componentStack.length - 1];
|
|
@@ -136,14 +147,14 @@ export function autoCloseMarkdown(markdown) {
|
|
|
136
147
|
for (let i = 0; i < propsContent.length; i++) {
|
|
137
148
|
if (propsContent[i] === '"')
|
|
138
149
|
dq++;
|
|
139
|
-
if (propsContent[i] === '
|
|
150
|
+
if (propsContent[i] === "'")
|
|
140
151
|
sq++;
|
|
141
152
|
}
|
|
142
153
|
let braceClose = '';
|
|
143
154
|
if (dq % 2 === 1)
|
|
144
155
|
braceClose += '"';
|
|
145
156
|
if (sq % 2 === 1)
|
|
146
|
-
braceClose += '
|
|
157
|
+
braceClose += "'";
|
|
147
158
|
result += braceClose + '}';
|
|
148
159
|
}
|
|
149
160
|
if (componentStack.length > 0) {
|
|
@@ -299,8 +310,11 @@ function closeInlineMarkersLinear(line) {
|
|
|
299
310
|
// Check if line starts with more than 3 asterisks (e.g., ****)
|
|
300
311
|
if (!(line[3] === '*')) {
|
|
301
312
|
// Check if marker at end with no content
|
|
302
|
-
if (!(contentEnd >= 3 &&
|
|
303
|
-
|
|
313
|
+
if (!(contentEnd >= 3 &&
|
|
314
|
+
line[contentEnd - 1] === '*' &&
|
|
315
|
+
line[contentEnd - 2] === '*' &&
|
|
316
|
+
line[contentEnd - 3] === '*' &&
|
|
317
|
+
(contentEnd === 3 || line[contentEnd - 4] === ' '))) {
|
|
304
318
|
closingSuffix = '***';
|
|
305
319
|
}
|
|
306
320
|
}
|
|
@@ -333,9 +347,9 @@ function closeInlineMarkersLinear(line) {
|
|
|
333
347
|
if (!allPaired) {
|
|
334
348
|
// Check if line ends with word (not just a closing marker)
|
|
335
349
|
const lastChar = line[contentEnd - 1];
|
|
336
|
-
const endsWithWord = (lastChar >= 'a' && lastChar <= 'z')
|
|
337
|
-
|
|
338
|
-
|
|
350
|
+
const endsWithWord = (lastChar >= 'a' && lastChar <= 'z') ||
|
|
351
|
+
(lastChar >= 'A' && lastChar <= 'Z') ||
|
|
352
|
+
(lastChar >= '0' && lastChar <= '9');
|
|
339
353
|
if (!hasCompleteBoldPair || endsWithWord) {
|
|
340
354
|
closingSuffix = '**';
|
|
341
355
|
if (hasTrailingSpace && !endsWithMarker) {
|
|
@@ -375,8 +389,7 @@ function closeInlineMarkersLinear(line) {
|
|
|
375
389
|
// Check marker at end with no content
|
|
376
390
|
// Only skip if it's truly isolated (e.g., "input *")
|
|
377
391
|
// Don't skip if there are complete pairs before it (e.g., "input **bold** *")
|
|
378
|
-
const markerAtEnd = contentEnd >= 1 && line[contentEnd - 1] === '*'
|
|
379
|
-
&& (contentEnd === 1 || line[contentEnd - 2] === ' ');
|
|
392
|
+
const markerAtEnd = contentEnd >= 1 && line[contentEnd - 1] === '*' && (contentEnd === 1 || line[contentEnd - 2] === ' ');
|
|
380
393
|
if (!markerAtEnd || asteriskCount > 1) {
|
|
381
394
|
closingSuffix = '*';
|
|
382
395
|
const endsWithMarker = line[contentEnd - 1] === '*';
|
|
@@ -427,8 +440,7 @@ function closeInlineMarkersLinear(line) {
|
|
|
427
440
|
}
|
|
428
441
|
if (validItalic) {
|
|
429
442
|
// Check marker at end with no content
|
|
430
|
-
const markerAtEnd = contentEnd >= 1 && line[contentEnd - 1] === '_'
|
|
431
|
-
&& (contentEnd === 1 || line[contentEnd - 2] === ' ');
|
|
443
|
+
const markerAtEnd = contentEnd >= 1 && line[contentEnd - 1] === '_' && (contentEnd === 1 || line[contentEnd - 2] === ' ');
|
|
432
444
|
if (!markerAtEnd) {
|
|
433
445
|
closingSuffix = '_';
|
|
434
446
|
const endsWithMarker = line[contentEnd - 1] === '_';
|
|
@@ -87,12 +87,10 @@ export function closeTables(markdown) {
|
|
|
87
87
|
const generateSeparator = () => '| ' + Array(columnCount).fill('---').join(' | ') + ' |';
|
|
88
88
|
// Check if separator exists (including incomplete ones with just :)
|
|
89
89
|
const secondLine = end - start >= 1 ? lines[start + 1].trim() : '';
|
|
90
|
-
const hasSeparator = secondLine.startsWith('|')
|
|
91
|
-
&& (secondLine.includes('-') || secondLine.includes(':'));
|
|
90
|
+
const hasSeparator = secondLine.startsWith('|') && (secondLine.includes('-') || secondLine.includes(':'));
|
|
92
91
|
// Handle last line
|
|
93
92
|
const lastLine = lines[end].trim();
|
|
94
|
-
const isSeparator = lastLine.startsWith('|')
|
|
95
|
-
&& (lastLine.includes('-') || lastLine.includes(':'));
|
|
93
|
+
const isSeparator = lastLine.startsWith('|') && (lastLine.includes('-') || lastLine.includes(':'));
|
|
96
94
|
if (isSeparator) {
|
|
97
95
|
// Parse and complete separator cells
|
|
98
96
|
const sepCells = parseCells(lastLine);
|
|
@@ -147,11 +145,16 @@ export function closeTables(markdown) {
|
|
|
147
145
|
const refWidths = parseCellWidths(refRow);
|
|
148
146
|
const cells = parseCells(lastLine);
|
|
149
147
|
// Rebuild with padding
|
|
150
|
-
lines[end] =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
lines[end] =
|
|
149
|
+
'| ' +
|
|
150
|
+
cells
|
|
151
|
+
.map((cell, i) => {
|
|
152
|
+
const targetWidth = refWidths[i] || cell.length + 2;
|
|
153
|
+
const padding = ' '.repeat(Math.max(0, targetWidth - cell.length - 2));
|
|
154
|
+
return cell + padding;
|
|
155
|
+
})
|
|
156
|
+
.join(' | ') +
|
|
157
|
+
' |';
|
|
155
158
|
}
|
|
156
159
|
// Add separator if missing
|
|
157
160
|
if (!hasSeparator) {
|
|
@@ -28,15 +28,7 @@ export function applyAutoUnwrap(node) {
|
|
|
28
28
|
}
|
|
29
29
|
// Check if we have exactly one paragraph child (and possibly empty text nodes)
|
|
30
30
|
if (nonEmptyChildren.length > 1 || typeof nonEmptyChildren[0] === 'string' || nonEmptyChildren[0][0] !== 'p') {
|
|
31
|
-
return [
|
|
32
|
-
tag,
|
|
33
|
-
props,
|
|
34
|
-
...children.map((child) => applyAutoUnwrap(child)),
|
|
35
|
-
];
|
|
31
|
+
return [tag, props, ...children.map((child) => applyAutoUnwrap(child))];
|
|
36
32
|
}
|
|
37
|
-
return [
|
|
38
|
-
tag,
|
|
39
|
-
props,
|
|
40
|
-
...nonEmptyChildren[0].slice(2),
|
|
41
|
-
];
|
|
33
|
+
return [tag, props, ...nonEmptyChildren[0].slice(2)];
|
|
42
34
|
}
|
|
@@ -20,7 +20,7 @@ export default function html_block(state, startLine, endLine, silent) {
|
|
|
20
20
|
// if it's indented more than 3 spaces, it should be a code block
|
|
21
21
|
if (state.sCount[startLine] - state.blkIndent >= 4)
|
|
22
22
|
return false;
|
|
23
|
-
if (state.src.charCodeAt(pos) !==
|
|
23
|
+
if (state.src.charCodeAt(pos) !== 0x3c /* < */)
|
|
24
24
|
return false;
|
|
25
25
|
let lineText = state.src.slice(pos, max);
|
|
26
26
|
let i = 0;
|
|
@@ -9,22 +9,18 @@ function isLinkClose(str) {
|
|
|
9
9
|
function isLetter(ch) {
|
|
10
10
|
/* eslint no-bitwise:0 */
|
|
11
11
|
const lc = ch | 0x20; // to lower case
|
|
12
|
-
return
|
|
12
|
+
return lc >= 0x61 /* a */ && lc <= 0x7a; /* z */
|
|
13
13
|
}
|
|
14
14
|
export default function html_inline(state, silent) {
|
|
15
15
|
// Check start
|
|
16
16
|
const max = state.posMax;
|
|
17
17
|
const pos = state.pos;
|
|
18
|
-
if (state.src.charCodeAt(pos) !==
|
|
19
|
-
|| /* < */ pos + 2 >= max) {
|
|
18
|
+
if (state.src.charCodeAt(pos) !== 0x3c || /* < */ pos + 2 >= max) {
|
|
20
19
|
return false;
|
|
21
20
|
}
|
|
22
21
|
// Quick fail on second char
|
|
23
22
|
const ch = state.src.charCodeAt(pos + 1);
|
|
24
|
-
if (ch !== 0x21
|
|
25
|
-
&& /* ! */ ch !== 0x3F
|
|
26
|
-
&& /* ? */ ch !== 0x2F
|
|
27
|
-
&& /* / */ !isLetter(ch)) {
|
|
23
|
+
if (ch !== 0x21 && /* ! */ ch !== 0x3f && /* ? */ ch !== 0x2f && /* / */ !isLetter(ch)) {
|
|
28
24
|
return false;
|
|
29
25
|
}
|
|
30
26
|
const match = state.src.slice(pos).match(HTML_TAG_RE);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Regexps to match html elements
|
|
2
2
|
const attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*';
|
|
3
3
|
const unquoted = '[^"\'=<>`\\x00-\\x20]+';
|
|
4
|
-
const single_quoted = '
|
|
4
|
+
const single_quoted = "'[^']*'";
|
|
5
5
|
const double_quoted = '"[^"]*"';
|
|
6
6
|
const attr_value = `(?:${unquoted}|${single_quoted}|${double_quoted})`;
|
|
7
7
|
const attribute = `(?:\\s+${attr_name}(?:\\s*=\\s*${attr_value})?)`;
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { Parser } from 'htmlparser2';
|
|
2
2
|
const VOID_ELEMENTS = new Set([
|
|
3
|
-
'area',
|
|
4
|
-
'
|
|
3
|
+
'area',
|
|
4
|
+
'base',
|
|
5
|
+
'br',
|
|
6
|
+
'col',
|
|
7
|
+
'embed',
|
|
8
|
+
'hr',
|
|
9
|
+
'img',
|
|
10
|
+
'input',
|
|
11
|
+
'link',
|
|
12
|
+
'meta',
|
|
13
|
+
'param',
|
|
14
|
+
'source',
|
|
15
|
+
'track',
|
|
16
|
+
'wbr',
|
|
5
17
|
]);
|
|
6
18
|
function attribsToComarkAttrs(attribs, isInline = false) {
|
|
7
19
|
const attrs = {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { kebabCase } from "../../../utils/index.js";
|
|
2
|
+
import { parseBracketContent } from "./brackets.js";
|
|
3
|
+
import { searchProps } from "./props.js";
|
|
4
|
+
const RE_BLOCK_NAME = /^[a-z$][$\w.-]*/i;
|
|
5
|
+
/**
|
|
6
|
+
* Parse `component-name [content] {.params}` from the trailing portion of a block fence line.
|
|
7
|
+
*/
|
|
8
|
+
export function parseBlockParams(str) {
|
|
9
|
+
str = str.trim();
|
|
10
|
+
if (!str)
|
|
11
|
+
return { name: '' };
|
|
12
|
+
const name = str.match(RE_BLOCK_NAME)?.[0];
|
|
13
|
+
if (!name)
|
|
14
|
+
throw new Error(`Invalid block params: ${str}`);
|
|
15
|
+
let remaining = str.slice(name.length).trim();
|
|
16
|
+
let content;
|
|
17
|
+
let props;
|
|
18
|
+
let unparsedRemaining;
|
|
19
|
+
if (remaining.startsWith('[')) {
|
|
20
|
+
const result = parseBracketContent(remaining, 0);
|
|
21
|
+
if (result) {
|
|
22
|
+
content = result.content;
|
|
23
|
+
remaining = remaining.slice(result.endIndex).trim();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (remaining.startsWith('{')) {
|
|
27
|
+
const propsResult = searchProps(remaining, 0);
|
|
28
|
+
if (propsResult) {
|
|
29
|
+
props = propsResult.props;
|
|
30
|
+
const afterProps = remaining.slice(propsResult.index).trim();
|
|
31
|
+
if (afterProps)
|
|
32
|
+
unparsedRemaining = afterProps;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (remaining) {
|
|
36
|
+
unparsedRemaining = remaining;
|
|
37
|
+
}
|
|
38
|
+
const result = {
|
|
39
|
+
name: kebabCase(name),
|
|
40
|
+
};
|
|
41
|
+
if (content !== undefined)
|
|
42
|
+
result.content = content;
|
|
43
|
+
if (props !== undefined)
|
|
44
|
+
result.props = props;
|
|
45
|
+
if (unparsedRemaining)
|
|
46
|
+
result.remaining = unparsedRemaining;
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse content within square brackets `[content]`.
|
|
3
|
+
* Returns the content (without the brackets) and the index just past the closing `]`.
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseBracketContent(str: string, startIndex: number): {
|
|
6
|
+
content: string;
|
|
7
|
+
endIndex: number;
|
|
8
|
+
} | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse content within square brackets `[content]`.
|
|
3
|
+
* Returns the content (without the brackets) and the index just past the closing `]`.
|
|
4
|
+
*/
|
|
5
|
+
export function parseBracketContent(str, startIndex) {
|
|
6
|
+
if (str[startIndex] !== '[')
|
|
7
|
+
return null;
|
|
8
|
+
let index = startIndex + 1;
|
|
9
|
+
while (index < str.length) {
|
|
10
|
+
if (str[index] === '\\' && index + 1 < str.length) {
|
|
11
|
+
index += 2;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (str[index] === ']') {
|
|
15
|
+
return { content: str.slice(startIndex + 1, index), endIndex: index + 1 };
|
|
16
|
+
}
|
|
17
|
+
index += 1;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const bracketPairs = {
|
|
2
|
+
'[': ']',
|
|
3
|
+
'{': '}',
|
|
4
|
+
'(': ')',
|
|
5
|
+
};
|
|
6
|
+
const quotePairs = {
|
|
7
|
+
"'": "'",
|
|
8
|
+
'"': '"',
|
|
9
|
+
'`': '`',
|
|
10
|
+
};
|
|
11
|
+
export function parseProps(content) {
|
|
12
|
+
content = content.trim();
|
|
13
|
+
if (!content)
|
|
14
|
+
return undefined;
|
|
15
|
+
const props = searchProps(content);
|
|
16
|
+
if (!props)
|
|
17
|
+
throw new Error(`Invalid props: \`${content}\``);
|
|
18
|
+
if (props.index !== content.length)
|
|
19
|
+
throw new Error(`Invalid props: \`${content}\`, expected end \`}\` but got \`${content.slice(props.index)}\``);
|
|
20
|
+
return props.props;
|
|
21
|
+
}
|
|
22
|
+
export function searchProps(content, index = 0) {
|
|
23
|
+
if (content[index] !== '{')
|
|
24
|
+
throw new Error(`Invalid props, expected \`{\` but got '${content[index]}'`);
|
|
25
|
+
const props = [];
|
|
26
|
+
// Skip Vue mustache `{{ }}` syntax
|
|
27
|
+
if (content[index + 1] === '{')
|
|
28
|
+
return undefined;
|
|
29
|
+
index += 1;
|
|
30
|
+
while (index < content.length) {
|
|
31
|
+
if (content[index] === '\\') {
|
|
32
|
+
index += 2;
|
|
33
|
+
}
|
|
34
|
+
else if (content[index] === '}') {
|
|
35
|
+
index += 1;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
else if (content[index] === ' ') {
|
|
39
|
+
index += 1;
|
|
40
|
+
}
|
|
41
|
+
else if (content[index] === '.') {
|
|
42
|
+
index += 1;
|
|
43
|
+
props.push(['class', searchUntil(' #.}')]);
|
|
44
|
+
}
|
|
45
|
+
else if (content[index] === '#') {
|
|
46
|
+
index += 1;
|
|
47
|
+
props.push(['id', searchUntil(' #.}')]);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const start = index;
|
|
51
|
+
while (index < content.length) {
|
|
52
|
+
index += 1;
|
|
53
|
+
if (' }='.includes(content[index]))
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
const char = content[index];
|
|
57
|
+
if (start !== index) {
|
|
58
|
+
const key = content.slice(start, index).trim();
|
|
59
|
+
if (char === '=') {
|
|
60
|
+
index += 1;
|
|
61
|
+
props.push([key, searchValue()]);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
props.push([key, 'true']);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function searchUntil(str) {
|
|
70
|
+
const start = index;
|
|
71
|
+
while (index < content.length) {
|
|
72
|
+
index += 1;
|
|
73
|
+
if (content[index] === '\\')
|
|
74
|
+
index += 2;
|
|
75
|
+
if (str.includes(content[index]))
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
return content.slice(start, index);
|
|
79
|
+
}
|
|
80
|
+
function searchValue() {
|
|
81
|
+
const start = index;
|
|
82
|
+
if (content[index] in bracketPairs) {
|
|
83
|
+
searchBracket(bracketPairs[content[index]]);
|
|
84
|
+
index += 1;
|
|
85
|
+
return content.slice(start, index);
|
|
86
|
+
}
|
|
87
|
+
else if (content[index] in quotePairs) {
|
|
88
|
+
searchString(quotePairs[content[index]]);
|
|
89
|
+
index += 1;
|
|
90
|
+
return content.slice(start, index);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
return searchUntil(' }');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function searchBracket(end) {
|
|
97
|
+
while (index < content.length) {
|
|
98
|
+
index++;
|
|
99
|
+
if (content[index] in quotePairs)
|
|
100
|
+
searchString(quotePairs[content[index]]);
|
|
101
|
+
else if (content[index] in bracketPairs)
|
|
102
|
+
searchBracket(bracketPairs[content[index]]);
|
|
103
|
+
else if (content[index] === end)
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function searchString(end) {
|
|
108
|
+
return searchUntil(end);
|
|
109
|
+
}
|
|
110
|
+
// Strip surrounding quotes from values
|
|
111
|
+
props.forEach((v) => {
|
|
112
|
+
if (/^(['"`]).*\1$/.test(v[1]))
|
|
113
|
+
v[1] = v[1].slice(1, -1);
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
props,
|
|
117
|
+
index,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -38,12 +38,11 @@ export function marmdownItTokensToComarkTree(tokens, options = { startLine: 0, p
|
|
|
38
38
|
if (options.preservePositions) {
|
|
39
39
|
for (let j = i; j < result.nextIndex; j++) {
|
|
40
40
|
if (tokens[j].map && tokens[j].map[1]) {
|
|
41
|
-
endLine = tokens[j].map[1]
|
|
42
|
-
+ options.startLine
|
|
43
|
-
+ (tokens[j].type?.endsWith('_close') ? 1 : 0);
|
|
41
|
+
endLine = tokens[j].map[1] + options.startLine + (tokens[j].type?.endsWith('_close') ? 1 : 0);
|
|
44
42
|
}
|
|
45
43
|
}
|
|
46
44
|
if (!result.node[1].$) {
|
|
45
|
+
;
|
|
47
46
|
result.node[1].$ = {};
|
|
48
47
|
}
|
|
49
48
|
;
|
|
@@ -73,7 +72,11 @@ function processAttributes(attrsArray, options = {}) {
|
|
|
73
72
|
continue;
|
|
74
73
|
}
|
|
75
74
|
// Handle boolean attributes: {bool} -> {":bool": "true"}
|
|
76
|
-
if (handleBoolean &&
|
|
75
|
+
if (handleBoolean &&
|
|
76
|
+
!key.startsWith(':') &&
|
|
77
|
+
!key.startsWith('#') &&
|
|
78
|
+
!key.startsWith('.') &&
|
|
79
|
+
(!value || value === 'true' || value === '')) {
|
|
77
80
|
attrs[`:${key}`] = 'true';
|
|
78
81
|
continue;
|
|
79
82
|
}
|
|
@@ -140,7 +143,7 @@ function parseCodeblockInfo(info) {
|
|
|
140
143
|
const trimmed = part.trim();
|
|
141
144
|
if (trimmed.includes('-')) {
|
|
142
145
|
// Range like "1-3"
|
|
143
|
-
const [start, end] = trimmed.split('-').map(s => Number.parseInt(s.trim(), 10));
|
|
146
|
+
const [start, end] = trimmed.split('-').map((s) => Number.parseInt(s.trim(), 10));
|
|
144
147
|
if (!Number.isNaN(start) && !Number.isNaN(end)) {
|
|
145
148
|
for (let i = start; i <= end; i++) {
|
|
146
149
|
highlights.push(i);
|
|
@@ -271,7 +274,10 @@ function processBlockToken(tokens, startIndex, insideNestedContext = false, stat
|
|
|
271
274
|
return { node: [componentName, attrs, ...children], nextIndex: nextIndex };
|
|
272
275
|
}
|
|
273
276
|
if (token.type === 'math_block') {
|
|
274
|
-
return {
|
|
277
|
+
return {
|
|
278
|
+
node: ['math', { class: 'math block', content: token.content }, token.content],
|
|
279
|
+
nextIndex: startIndex + 1,
|
|
280
|
+
};
|
|
275
281
|
}
|
|
276
282
|
if (token.type === 'fence' || token.type === 'fenced_code_block' || token.type === 'code_block') {
|
|
277
283
|
const content = token.content || '';
|
|
@@ -312,7 +318,10 @@ function processBlockToken(tokens, startIndex, insideNestedContext = false, stat
|
|
|
312
318
|
const textContent = extractTextContent(children.nodes);
|
|
313
319
|
const headingId = uniqueSlug(slugify(textContent), level, state);
|
|
314
320
|
// Always attach ID to the heading element itself
|
|
315
|
-
return {
|
|
321
|
+
return {
|
|
322
|
+
node: [headingTag, { id: headingId }, ...children.nodes],
|
|
323
|
+
nextIndex: children.nextIndex + 1,
|
|
324
|
+
};
|
|
316
325
|
}
|
|
317
326
|
return { node: null, nextIndex: children.nextIndex + 1 };
|
|
318
327
|
}
|
|
@@ -320,19 +329,8 @@ function processBlockToken(tokens, startIndex, insideNestedContext = false, stat
|
|
|
320
329
|
if (token.type === 'list_item_open') {
|
|
321
330
|
const attrs = processAttributes(token.attrs, { handleBoolean: false, handleJSON: false });
|
|
322
331
|
const children = processBlockChildren(tokens, startIndex + 1, 'list_item_close', false, false, true, state);
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
for (const child of children.nodes) {
|
|
326
|
-
if (Array.isArray(child) && child[0] === 'p') {
|
|
327
|
-
// Unwrap paragraph, add its children directly
|
|
328
|
-
unwrapped.push(...child.slice(2));
|
|
329
|
-
}
|
|
330
|
-
else {
|
|
331
|
-
unwrapped.push(child);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
if (unwrapped.length > 0) {
|
|
335
|
-
return { node: ['li', attrs, ...unwrapped], nextIndex: children.nextIndex + 1 };
|
|
332
|
+
if (children.nodes.length > 0) {
|
|
333
|
+
return { node: ['li', attrs, ...children.nodes], nextIndex: children.nextIndex + 1 };
|
|
336
334
|
}
|
|
337
335
|
return { node: null, nextIndex: children.nextIndex + 1 };
|
|
338
336
|
}
|
|
@@ -615,7 +613,7 @@ function processInlineToken(tokens, startIndex, inHeading = false) {
|
|
|
615
613
|
return { node, nextIndex: j };
|
|
616
614
|
}
|
|
617
615
|
// Handle Comark inline span (e.g., [text]{attr})
|
|
618
|
-
//
|
|
616
|
+
// The syntax plugin emits mdc_inline_span tokens, and props appear AFTER the close token
|
|
619
617
|
if (token.type === 'mdc_inline_span' && token.nesting === 1) {
|
|
620
618
|
const attrs = {};
|
|
621
619
|
let i = startIndex + 1;
|
|
@@ -698,11 +696,11 @@ function processInlineToken(tokens, startIndex, inHeading = false) {
|
|
|
698
696
|
else {
|
|
699
697
|
// Self-closing component (nesting === 0)
|
|
700
698
|
const attrs = {};
|
|
701
|
-
//
|
|
699
|
+
// The syntax plugin stores attributes in a separate mdc_inline_props token
|
|
702
700
|
// that appears right after the component token
|
|
703
701
|
const { attrs: componentAttrs, nextIndex: propsNextIndex } = extractAttributes(tokens, startIndex + 1, false);
|
|
704
702
|
Object.assign(attrs, componentAttrs);
|
|
705
|
-
// Extract attributes from token.attrs (fallback, though
|
|
703
|
+
// Extract attributes from token.attrs (fallback, though the syntax plugin uses mdc_inline_props)
|
|
706
704
|
const fallbackAttrs = processAttributes(token.attrs, { handleBoolean: false });
|
|
707
705
|
Object.assign(attrs, fallbackAttrs);
|
|
708
706
|
// Return the component without any text children
|
|
@@ -734,7 +732,10 @@ function processInlineToken(tokens, startIndex, inHeading = false) {
|
|
|
734
732
|
return { node: null, nextIndex };
|
|
735
733
|
}
|
|
736
734
|
if (token.type === 'math_inline') {
|
|
737
|
-
return {
|
|
735
|
+
return {
|
|
736
|
+
node: ['math', { class: 'math inline', content: token.content }, token.content],
|
|
737
|
+
nextIndex: startIndex + 1,
|
|
738
|
+
};
|
|
738
739
|
}
|
|
739
740
|
// Handle generic inline open/close pairs
|
|
740
741
|
const tagName = INLINE_TAG_MAP[token.type];
|